'use client'; import './style.scss'; import Link from 'next/link'; import Image from 'next/image'; import { redirect, useRouter } from 'next/navigation'; import { useState, useEffect, useCallback, MouseEvent } from 'react'; import { Menu } from 'lucide-react'; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from '@/components/ui/dropdown-menu'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { faBookmark as nBookmark, faThumbsUp as nThumbsUp, faThumbsDown as nThumbsDown, faFlag as nFlag, faPenToSquare, faTrashCan } from '@fortawesome/free-regular-svg-icons'; import { faQrcode, faPrint, faLink, faShareNodes, faBookmark as yBookmark, faThumbsUp as yThumbsUp, faThumbsDown as yThumbsDown, faFlag as yFlag } from '@fortawesome/free-solid-svg-icons'; import Loading from '@/app/component/Loading'; import Comment from '@/app/(forum)/comment/view'; import LatestList from '@/app/(forum)/post/_component/LatestPosts'; import BoardResponse from '@/dtos/response/forum/board/boardResponse'; import PostResponse from '@/dtos/response/forum/post/postResponse'; import PostReactionRequest from '@/dtos/request/forum/post/postReactionRequest'; import PostBookmarkRequest from '@/dtos/request/forum/post/postReactionRequest'; import Content from '../_component/Content'; import QRCode from '../_component/QRCode'; import Copied from '../_component/Copied'; import SnsShare from '../_component/SnsShare'; import Report from '../_component/Report'; import { Reaction } from '@/constants/forum'; import { fetchPostReaction, fetchPostBookmark, fetchPostDelete } from '@/lib/api/forum/post'; import { getDateTime, throwError, formatDate, isDateOverdue } from '@/lib/utils/client'; import useAuth from '@/hooks/useAuth'; type Props = { _board: BoardResponse, _post: PostResponse }; export default function View({ _board, _post }: Props) { useEffect(() => { // 신고 횟수 초과 게시글은 접근 불가 if (_post.reports > _board.boardMeta.view.blameHideCount && _board.boardMeta.view.blameHideCount > 0) { alert('비공개 게시글입니다.'); redirect(`/board/${_post.boardCode}${window.location.search}`); } }, []); const router = useRouter(); const { member, isLoggedIn } = useAuth(); const [error, setError] = useState(''); const [loading, setLoading] = useState(false); const [qrCode, setQrCode] = useState(false); const [copied, setCopied] = useState(false); const [snsShare, setSnsShare] = useState(false); const [report, setReport] = useState(false); const [hasLike, setHasLike] = useState(_post.hasLike); const [hasDisLike, setHasDisLike] = useState(_post.hasDislike); const [hasBookmark, setHasBookmark] = useState(_post.hasBookmark); const [hasReport, setHasReport] = useState(_post.hasReport); useEffect(() => { if (error) { alert(error); setError(''); } }, [error]); const toggleQRCode = useCallback((e: MouseEvent) => { e.preventDefault(); setQrCode((prev) => !prev); }, []); const handlePrint = useCallback(() => { window.print(); }, []); const toggleCopied = useCallback((e: MouseEvent) => { e.preventDefault(); setCopied((prev) => !prev); }, []); const toggleSnsShare = useCallback((e: MouseEvent) => { e.preventDefault(); setSnsShare((prev) => !prev); }, []); // 좋아요/싫어요 const handleReaction = useCallback(async (e: MouseEvent) => { const reaction = Number(e.currentTarget.value); if (!await isLoggedIn()) { return; } fetchPostReaction({ postID: _post.id, reaction: reaction } as PostReactionRequest).then(res => { if (res.success) { switch (reaction) { case Reaction.Like: setHasLike(!hasLike); setHasDisLike(false); break; case Reaction.Dislike: setHasDisLike(!hasDisLike); setHasLike(false); break; } } else { throwError(res); } }).catch((err) => { setError(err.message); }).finally(() => { setLoading(false); }); }, [member, hasLike, hasDisLike]); // 즐겨찾기 const handleBookmark = useCallback(async () => { if (!await isLoggedIn()) { return; } fetchPostBookmark({ postID: _post.id } as PostBookmarkRequest).then(res => { if (res.success) { setHasBookmark(!hasBookmark); } else { throwError(res); } }).catch((err) => { setError(err.message); }).finally(() => { setLoading(false); }); }, [member, hasBookmark]); // 신고하기 시작 const handleReport = useCallback(async () => { if (hasReport) { alert('이미 신고하셨습니다.'); return; } if (!await isLoggedIn()) { return; } setReport((prev) => !prev); }, [member, hasReport]); // 수정하기 const handleEdit = useCallback(async () => { if (!await isLoggedIn()) { return; } // 게시글 삭제 보호 확인 if (_board.boardMeta.general.allowUpdateProtection && !member?.isAdmin) { if (isDateOverdue(_post.createdAt, _board.boardMeta.general.updateProtectionDays)) { return alert(`게시글 작성 후 ${_board.boardMeta.general.updateProtectionDays}일이 지나 수정이 불가능합니다.`); } } router.push(`/post/edit/${_post.id}`); }, [member]); // 게시글 삭제 const handleDelete = useCallback(async () => { if (!await isLoggedIn()) { return; } // 게시글 삭제 보호 확인 if (_board.boardMeta.general.allowDeleteProtection && !member?.isAdmin) { if (isDateOverdue(_post.createdAt, _board.boardMeta.general.deleteProtectionDays)) { return alert(`게시글 작성 후 ${_board.boardMeta.general.deleteProtectionDays}일이 지나 삭제가 불가능합니다.`); } } if (confirm('정말 삭제하시겠습니까?')) { fetchPostDelete(_post.id).then(res => { if (res.success) { alert('게시글이 삭제되었습니다.'); router.push(`/board/${_post.boardCode}`); } else { throwError(res); } }).catch((err) => { setError(err.message); }).finally(() => { setLoading(false); }); } }, [member]); return (
{loading && } {/* 글 제목 */}
{_post.boardPrefixID && ("[" + _post.boardPrefix.name + "]")} {_post.subject}

{/* 글 작성자/작성일시/부가기능들 */}
{_board.boardMeta.view.showMemberThumb && (
회원 사진
)}
  • ※ {_post.writer.name}
  • {_board.boardMeta.view.showMemberRegDate &&
  • {formatDate(_post.writer.createdAt)} 가입
  • } {_board.boardMeta.view.showMemberSummary &&
  • {_post.writer.summary}
  • }
  • 조회: {_post.views}
  • 댓글: {_post.comments}
  • {_board.boardMeta.view.allowLike && (
  • 좋아요: {_post.likes}
  • )} {_board.boardMeta.view.allowDislike && (
  • 싫어요: {_post.dislikes}
  • )}
  • IP: {_post.ipAddress}
작성일시 : {getDateTime(_post.createdAt)}
    {_board.boardMeta.view.allowPostUrlQrCode && (
  • QR
  • )} {_board.boardMeta.view.allowPrint && (
  • 인쇄
  • )} {_board.boardMeta.view.allowPostUrlCopy && (
  • 주소
  • )} {_board.boardMeta.view.allowSnsShare && (
  • 공유
  • )}

{/* 글 내용 */}
{_post.tagList.length > 0 && (
{/* 태그 표시 */} {_post.tagList.map((row, i) => ( #{row.slug} ))}
)}

{/* 제어 버튼들 */}
목록 {_board.boardMeta.view.allowPrevNextBotton && ( <> {!!_post.prevID && ( 이전 )} {!!_post.nextID && ( 다음 )} )}
{_board.boardMeta.view.allowLike && (
)} {_board.boardMeta.view.allowDislike && (
)} {_board.boardMeta.view.allowBookmark && (
)} {_board.boardMeta.view.allowBlame && (
)}
{_board.boardMeta.view.allowLike && ( )} {_board.boardMeta.view.allowDislike && ( )} {_board.boardMeta.view.allowBookmark && ( 즐겨찾기 )} {_board.boardMeta.view.allowBlame && ( 신고 )} 수정 삭제

{/* 댓글 */} {/* 게시판 최근 글 */}
); }